package foundation;

import java.io.InputStream;
import java.net.Socket;
import java.util.Observable;
import java.util.Observer;

public class FDataStreamReader extends Observable implements Runnable
{
    private volatile boolean run = false;
    
    private final int RECONNECT_DELAY = 5000;
    private String address;
    private int port;
    private boolean firstError = true;
    private FStatusNotifier statusNotifier = new FStatusNotifier();
    private String[] okText =
    {
        "OK", "Data connection ok."
    };
    private String[] errText =
    {
        "NC", "No data connection to car. Check error log."
    };

    private enum State
    {

        readingFirstLine, readingSecondLine, readingOD;
    }

    public FDataStreamReader(String address, int port)
    {
        this.address = address;
        this.port = port;
        statusNotifier = new FStatusNotifier();
    }
    
    public FDataStreamReader(FDataStreamReader source, String address, int port)
    {
        this.address = address;
        this.port = port;
        statusNotifier = source.statusNotifier;
        source.statusNotifier = new FStatusNotifier();
    }

    public boolean isRunning()
    {
        return run;
    }

    public void addStatusObserver(Observer obs)
    {
        statusNotifier.addObserver(obs);
    }

    public void deleteStatusObserver(Observer obs)
    {
        statusNotifier.deleteObserver(obs);
    }

    @Override
    public void run()
    {
        run = true;
        
        Socket socket = null;
        InputStream istream;
        
        while (run)
        {
            try
            {
                socket = new Socket(address, port);
                socket.setSoTimeout(5000);
                istream = socket.getInputStream();
                int readByte;
                byte[] readOD = new byte[2048];
                String line = "";
                int bytesRead = 0;
                State state = State.readingFirstLine;
                firstError = true;
                statusNotifier.setChanged();
                statusNotifier.notifyObservers(okText);

                while ((readByte = istream.read()) != -1)
                {
                    switch (state)
                    {
                        case readingFirstLine:
                            if (readByte != 10)
                            {
                                line += (char) readByte;
                            }
                            else
                            {
                                if (line.equalsIgnoreCase("OD Data"))
                                {
                                    state = State.readingSecondLine;
                                }
                                line = "";
                            }
                            break;
                        case readingSecondLine:
                            if (readByte != 10)
                            {
                                line += (char) readByte;
                            }
                            else
                            {
                                line = "";
                                state = State.readingOD;
                            }
                            break;
                        case readingOD:
                            readOD[bytesRead++] = (byte) readByte;

                            if (bytesRead == 2048)
                            {
                                bytesRead = 0;
                                setChanged();
                                notifyObservers(readOD);
                                state = State.readingFirstLine;
                                // Log data to file
//                                try (DataOutputStream output = new DataOutputStream(new FileOutputStream(
//                                                "pse1a-formula-data.bin", true)))
//                                {
//                                    output.write(readOD);
//                                }
//                                catch (FileNotFoundException ex)
//                                {
//                                    System.err.println("PSE1-A: Output file not found");
//                                }
//                                catch (IOException ex)
//                                {
//                                    System.out.println("PSE1-A: There was an IOException while saving formula stream to file. Details:");
//                                    System.out.println(ex);
//                                }
                            }

                            break;
                    }
                    
                    if ( ! run)
                    {
                        break;
                    }
                }
                
                socket.close();
                
                if (run && firstError)
                {
                    String error = "Data connection to car lost. Using address"
                            + "=" + address + " and port=" + port + ". Retrying "
                            + "connection every " + RECONNECT_DELAY / 1000 + "s";
                    setChanged();
                    notifyObservers(error);
                    statusNotifier.setChanged();
                    statusNotifier.notifyObservers(errText);
                    firstError = false;
                }
                
                else if (! run)
                {
                    // It was requested to close streaming
                    setChanged();
                    notifyObservers("Closing formula connection");
                    statusNotifier.setChanged();
                    statusNotifier.notifyObservers(errText);
                    break;
                }
            }
            catch (Exception e)
            {
                try
                {
                    socket.close();
                }
                catch (Exception e1)
                {
                }

                if (firstError)
                {
                    e.printStackTrace();

                    String error = "No data connection to car. Using address"
                            + "=" + address + " and port=" + port + ". Retrying "
                            + "connection every " + RECONNECT_DELAY / 1000 + "s";
                    setChanged();
                    notifyObservers(error);
                    statusNotifier.setChanged();
                    statusNotifier.notifyObservers(errText);
                    firstError = false;
                }
            }

            try
            {
                Thread.sleep(RECONNECT_DELAY);
            }
            catch (InterruptedException e)
            {
            }
        }
    }

    public int convertNumber(String number, int format)
    {
        return 0;
    }
    
    public void stop()
    {
        run = false;
        statusNotifier.setChanged();
        statusNotifier.notifyObservers(errText);
    }
    
}
